package com.lambkit.core.config;

import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.lambkit.core.config.annotation.PropConfig;
import com.lambkit.core.exception.LambkitException;

import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 配置管理类
 * <p>
 * 用于读取配置信息，包括本地配置信息和分布式远程配置信息
 */
public class ConfigCenterContainer {
    private static final Log log = LogFactory.get();

    private ConcurrentHashMap<String, Object> configs = new ConcurrentHashMap<>();
    private ConcurrentHashMap<String, String> values = new ConcurrentHashMap<>();
    private List<ConfigCenter> configCenters = new CopyOnWriteArrayList<ConfigCenter>();

    public ConfigCenterContainer() {
    }

    public void addConfigCenter(ConfigCenter configCenter) {
        configCenters.add(configCenter);
    }

    public void addConfigCenter(ConfigCenter configCenter, int index) {
        configCenters.add(index, configCenter);
    }

    public int size() {
        return configCenters.size();
    }

    public void clearConfig() {
        configs.clear();
    }

    public void clearConfig(String className) {
        configs.remove(className);
    }
    /**
     * 刷新、重载配置
     */
    public void refresh() {
        configs.clear();
        for (ConfigCenter configCenter : configCenters) {
            configCenter.refresh();
        }
    }

    public void refresh(String name) {
        configs.clear();
        for (ConfigCenter configCenter : configCenters) {
            if(configCenter.getName().equals(name)) {
                configCenter.refresh();
            }
        }
    }

    public <T>T getValue(String key, Class clazz) {
        String value = getValue(key);
        if (StrUtil.isNotBlank(value)) {
            if(clazz!=null) {
                return (T) convert(clazz, value);
            }
        }
        return null;
    }

    public <T>T getValue(String key, Class clazz, T defaultValue) {
        T value = getValue(key, clazz);

        if (value!=null) {
            return value;
        }
        return defaultValue;
    }

    public String getValue(String key, String defaultValue) {
        String value = getValue(key);

        if (StrUtil.isNotBlank(value)) {
            return value;
        }
        return defaultValue;
    }

    public String getValue(String key) {
        return getValueExclude(key, null);
    }

    /**
     * 根据 key 获取value的值
     * <p>
     * 优先获取系统启动设置参数
     * 第二 获取远程配置
     * 第三 获取本地配置
     * @param key
     * @return
     */

    public String getValueExclude(String key, Class<? extends ConfigCenter> excludeConfigCenterClass) {
        String value = values.get(key);
        if(StrUtil.isNotBlank(value)) {
            return value;
        }
        for (ConfigCenter configCenter : configCenters) {
            if(excludeConfigCenterClass!=null
                    && excludeConfigCenterClass.isAssignableFrom(configCenter.getClass())) {
                continue;
            }
            value = configCenter.getValue(key);
            if(StrUtil.isNotBlank(value)) {
                values.put(key, value);
                //System.out.println("ConfigCenterContainer.getValue(" + key + ")=" + value + " from " + configCenter.getName());
                break;
            }
        }
        return value;
    }

    public void setValue(String name, String key, String value) {
        if(StrUtil.isNotBlank(key) && StrUtil.isNotBlank(value)) {
            for (ConfigCenter configCenter : configCenters) {
                configCenter.setValue(name, key, value);
            }
            clearConfig();
        }
    }

    public boolean containsKey(String key) {
        for (ConfigCenter configCenter : configCenters) {
            boolean flag = configCenter.containsKey(key);
            if(flag) {
                return true;
            }
        }
        return false;
    }

    public List<String> getKeys(String prefix) {
        List<String> keyList = new ArrayList<>();
        for (ConfigCenter configCenter : configCenters) {
            keyList.addAll(configCenter.getKeys(prefix));
        }
        return keyList;
    }

    public <T> T get(Class<T> clazz) {
        PropConfig propConfig = clazz.getAnnotation(PropConfig.class);
        if (propConfig == null) {
            return get(clazz, null, null);
        }
        return get(clazz, propConfig.prefix(), null);
    }

    public <T> T get(Class<T> clazz, Class<? extends ConfigCenter> excludeConfigCenterClass) {
        PropConfig propConfig = clazz.getAnnotation(PropConfig.class);
        if (propConfig == null) {
            return get(clazz, null, excludeConfigCenterClass);
        }
        return get(clazz, propConfig.prefix(), excludeConfigCenterClass);
    }

    public <T> T get(Class<T> clazz, String prefix) {
        return get(clazz, prefix, null);
    }


    /**
     * 获取配置信息，并创建和赋值clazz实例
     *
     * @param clazz  指定的类
     * @param prefix 配置文件前缀
     * @param <T>
     * @return
     */
    public <T> T get(Class<T> clazz, String prefix, Class<? extends ConfigCenter> excludeConfigCenterClass) {
        T obj = (T) configs.get(clazz.getName() + prefix);
        if (obj != null) {
            return obj;
        }

        obj = ReflectUtil.newInstance(clazz);

        List<Method> setMethods = new ArrayList<>();

        Method[] methods = obj.getClass().getMethods();
        if (isNotEmpty(methods)) {
            for (Method m : methods) {
                if (m.getName().startsWith("set")
                        && m.getName().length() > 3
                        && m.getParameterTypes().length == 1) {//java7
                    //&& m.getParameterCount() == 1) {//java8
                    setMethods.add(m);
                }
            }
        }

        for (Method method : setMethods) {

            String key = getKeyByMethod(prefix, method);
            String value = getValueExclude(key, excludeConfigCenterClass);

            try {
                if (StrUtil.isNotBlank(value)) {
                    Object val = convert(method.getParameterTypes()[0], value);
                    method.invoke(obj, val);
                }
            } catch (Throwable ex) {
                log.error(ex.toString(), ex);
            }
        }

        configs.put(clazz.getName() + prefix, obj);

        return obj;
    }

    boolean isNotEmpty(Object[] objects) {
        return objects != null && objects.length > 0;
    }

    private String getKeyByMethod(String prefix, Method method) {
        String key = firstCharToLowerCase(method.getName().substring(3));
        if (StrUtil.isNotBlank(prefix)) {
            key = prefix.trim() + "." + key;
        }

        return key;
    }

    private String firstCharToLowerCase(String str) {
        char firstChar = str.charAt(0);
        if (firstChar >= 'A' && firstChar <= 'Z') {
            char[] arr = str.toCharArray();
            arr[0] += ('a' - 'A');
            return new String(arr);
        }
        return str;
    }

    /**
     * 数据转化
     *
     * @param type
     * @param s
     * @return
     */
    private static final Object convert(Class<?> type, String s) {
        if (type == String.class) {
            return s;
        }

        // mysql type: int, integer, tinyint(n) n > 1, smallint, mediumint
        if (type == Integer.class || type == int.class) {
            return Integer.parseInt(s);
        }

        // mysql type: bigint
        if (type == Long.class || type == long.class) {
            return Long.parseLong(s);
        }

        // mysql type: real, double
        if (type == Double.class || type == double.class) {
            return Double.parseDouble(s);
        }

        // mysql type: float
        if (type == Float.class || type == float.class) {
            return Float.parseFloat(s);
        }

        // mysql type: bit, tinyint(1)
        if (type == Boolean.class || type == boolean.class) {
            String value = s.toLowerCase();
            if ("1".equals(value) || "true".equals(value)) {
                return Boolean.TRUE;
            } else if ("0".equals(value) || "false".equals(value)) {
                return Boolean.FALSE;
            } else {
                throw new RuntimeException("Can not parse to boolean type of value: " + s);
            }
        }

        // mysql type: decimal, numeric
        if (type == java.math.BigDecimal.class) {
            return new java.math.BigDecimal(s);
        }

        // mysql type: unsigned bigint
        if (type == java.math.BigInteger.class) {
            return new java.math.BigInteger(s);
        }

        // mysql type: binary, varbinary, tinyblob, blob, mediumblob, longblob. I have not finished the test.
        if (type == byte[].class) {
            return s.getBytes();
        }

//        if(type == NodeType.class) {
//        	return NodeType.valueOf(s);
//        }
        throw new LambkitException(type.getName() + " can not be converted, please use other type in your config class!");
    }

}
